import {
  Constraint,
  ConstraintType,
  EPSILON,
  FeatureManager,
  ICircleParams,
  ICurve,
  ILineParams,
} from '@amcax/base';
import { basicSketchIds } from '@app-cad/common/utils';
import { ConstraintIconGroup } from '@app-base/common/utils/renderObjects.ts/ConstraintIconGroup';
import { ctx2 } from '@app-cad/module';
import { isEqual } from 'lodash';
import {
  Matrix4,
  Scene,
  Vector2,
  Vector2tuple,
  Vector3,
  Vector3Tuple,
} from 'three-legacy';

export const ConstraintIconEvents = {
  SelectFromCanvas: 'selectConstraintFromCanvas',
} as const;

export type ConstraintData = Constraint & {
  parent: ICurve;
};

export type ConstraintTag = {
  constraint: ConstraintData;
  position: Vector2tuple;
  // offset 为空则表示按默认的向上偏移处理
  offset?: Vector2tuple;
};

// 一个约束可能和多个元素相关，此时，有可能会渲染多个标注
type ConstraintRenderTag = {
  data: ConstraintTag[];
  renderObj?: ConstraintIconGroup;
};

export class ConstraintIconRenderer {
  // 记录每个2d元素下从属的标注
  renderDataByICurve: Map<
    string,
    { icurve: ICurve; constraints: ConstraintData[] }
  > = new Map();
  // 记录每个位置下渲染的标注
  // key粗暴使用坐标+offset(保留一定位数)作为key，需求上重合/非常接近的数值应当被归入同组
  renderDataByRenderTarget: Map<string, ConstraintRenderTag> = new Map();

  renderGroup: Scene;

  renderedObjects: Set<ConstraintIconGroup> = new Set();

  // 记录当前草图的坐标和草图点转换到世界坐标所需的转换矩阵
  private _frame: Vector3Tuple[];
  _transformMatrix: Matrix4;

  get frame() {
    return this._frame;
  }

  set frame(val) {
    this._frame = val;
    const worldMatrix = new Matrix4().makeBasis(
      new Vector3(1, 0, 0),
      new Vector3(0, 1, 0),
      new Vector3(0, 0, 1),
    );

    const zBasis = new Vector3(...this._frame[1]).normalize();
    const xBasis = new Vector3(...this._frame[2]).normalize();
    const yBasis = zBasis.clone().cross(xBasis);
    const sketchMatrix = new Matrix4().makeBasis(xBasis, yBasis, zBasis);

    this._transformMatrix = worldMatrix
      .clone()
      .transpose()
      .multiply(sketchMatrix);
  }

  get transformMatrix() {
    return this._transformMatrix;
  }

  constructor() {
    const workGroup = ctx2.cadScene.workGroup;
    const name = 'constraintIconGroup';

    const existGroup = workGroup.children.find((s) => s.name === name);
    if (existGroup != null) {
      existGroup.clear();
      this.renderGroup = existGroup as Scene;
      return;
    }

    this.renderGroup = new Scene();
    this.renderGroup.name = name;
    ctx2.cadScene.workGroup.add(this.renderGroup);
  }

  // 每次更新前清空，更新后对数据进行集中处理
  private reRenderKeys: Set<string> = new Set();

  // 重新渲染，被标记的2d元素删除原先的渲染，重新绘制
  private rerender() {
    this.reRenderKeys.forEach((renderTarget) => {
      const { data, renderObj } =
        this.renderDataByRenderTarget.get(renderTarget) ?? {};

      renderObj?.destroy();
      this.renderedObjects.delete(renderObj);

      if (data?.length > 0) {
        const newTag = new ConstraintIconGroup(data);

        newTag.addTo(this.renderGroup);
        this.renderedObjects.add(newTag);

        this.renderDataByRenderTarget.set(renderTarget, {
          data,
          renderObj: newTag,
        });
      }
    });

    this.reRenderKeys = new Set();
    ctx2.viewer.requestRender();

    Array.from(this.renderDataByRenderTarget.keys()).forEach((key) => {
      const val = this.renderDataByRenderTarget.get(key);

      if (val.data.length === 0) {
        this.renderDataByRenderTarget.delete(key);
      }
    });
  }

  getDrawID(parent: ICurve, constraint: Constraint, drawTo: string) {
    return `${parent.id}_${constraint.type}_${drawTo}`;
  }

  // 是否是同一个约束（不代表参数也相等）
  isSameConstraint(a: Constraint, b: Constraint) {
    return (
      a.itemid === b.itemid &&
      a.target === b.target &&
      a.axis === b.axis &&
      a.type === b.type
    );
  }

  // 按2d元素新赠标签渲染
  addICurves(icurves: ICurve[]) {
    icurves.forEach((icurve) => {
      if (this.renderDataByICurve.get(icurve.id) == null) {
        this.renderDataByICurve.set(icurve.id, { icurve, constraints: [] });
      }
    });

    icurves.forEach((icurve) => {
      icurve.constraints.forEach((c) => {
        this.addConstraint({ ...c, parent: icurve });
      });
    });

    this.rerender();
  }
  // 按2d元素移除标签渲染
  removeICurves(icurves: string[]) {
    this.getConstraintToRemoveByICurve(icurves).forEach((c) =>
      this.removeConstraint(c),
    );

    this.rerender();
  }

  private getConstraintToRemoveByICurve(icurves: string[]) {
    const result: ConstraintData[] = [];
    Array.from(this.renderDataByICurve.values()).forEach((v) => {
      v.constraints.forEach((c) => {
        if (
          [c.parent.id, c.target, c.itemid].some((id) => icurves.includes(id))
        ) {
          result.push(c);
        }
      });
    });

    return result;
  }

  clear() {
    Array.from(this.renderDataByRenderTarget.values()).forEach((v) =>
      v.renderObj?.destroy(),
    );
    this.reRenderKeys = new Set();
    this.renderDataByRenderTarget = new Map();
    this.renderDataByICurve = new Map();
  }

  private getOffsetOfICurve(icurveID: string, position: Vector2tuple) {
    const icurve = this.renderDataByICurve.get(icurveID)?.icurve;

    if (icurve == null || position == null || icurve.type === 'Point') {
      return null;
    }

    if (icurve.type === 'Line') {
      const { point1, point2 } = icurve.params as ILineParams;
      const vector = new Vector3(
        point1[0] - point2[0],
        point1[1] - point2[1],
        0,
      );

      vector.cross(new Vector3(0, 0, 1));

      if (vector.y < 0 || (vector.y === 0 && vector.x > 0)) {
        vector.negate();
      }

      return vector.normalize().toArray().slice(0, 2) as Vector2tuple;
    }

    if (icurve.type === 'ArcOfCircle' || icurve.type === 'Circle') {
      const { center } = icurve.params as ICircleParams;
      const vector = new Vector2(
        position[0] - center[0],
        position[1] - center[1],
      );

      // 若是圆心，则放在右侧
      if (vector.length() < EPSILON) {
        return [1, 0] as Vector2tuple;
      }

      // 不然按半径方向向外偏移
      return vector.normalize().toArray();
    }
    return null;
  }

  // 获取标签的挂载位置
  private getDrawToAndOffsets = (constraint: ConstraintData) => {
    // X、Y轴的图标位置不跟长度走
    const getPos = (icurveId: string, pos: Vector2tuple) => {
      if (icurveId.includes('X-AXIS')) {
        return [5, 0] as Vector2tuple;
      }
      if (icurveId.includes('Y-AXIS')) {
        return [0, 5] as Vector2tuple;
      }

      return pos;
    };

    const itempos = getPos(constraint.itemid, constraint.itempos);
    const targetpos = getPos(constraint.target, constraint.targetpos);

    const isSamePosition = isEqual(itempos, targetpos);

    if (!isSamePosition) {
      return [
        {
          drawTo: itempos,
          offset: this.getOffsetOfICurve(constraint.itemid, itempos),
        },
        {
          drawTo: targetpos,
          offset: this.getOffsetOfICurve(constraint.target, targetpos),
        },
      ];
    }

    if (constraint.type === ConstraintType.MidPoint) {
      return [
        {
          drawTo: targetpos,
          offset: this.getOffsetOfICurve(constraint.target, targetpos),
        },
      ];
    }

    if (constraint.type === ConstraintType.Coincident) {
      const targetType = this.renderDataByICurve.get(constraint.target)?.icurve
        .type;
      const itemType = this.renderDataByICurve.get(constraint.itemid)?.icurve
        .type;
      return targetType === itemType
        ? [
            {
              drawTo: targetpos,
              offset: null,
            },
          ]
        : [];
    }

    if (constraint.type === ConstraintType.Perpendicular) {
      const icurveA = this.renderDataByICurve.get(constraint.itemid)?.icurve;
      const icurveB = this.renderDataByICurve.get(constraint.target)?.icurve;

      const calcLineBasis = (params: ILineParams, position: Vector2tuple) => {
        const { point1, point2 } = params;
        // 起点到标注点
        const vectorAO = new Vector2(
          position[0] - point1[0],
          position[1] - point1[1],
        );

        // 标注点到终点
        const vectorOB = new Vector2(
          point2[0] - position[0],
          point2[1] - position[1],
        );

        if (vectorAO.equals(vectorOB)) {
          return vectorAO.clone().normalize();
        }

        return vectorAO.clone().sub(vectorOB).normalize();
      };

      const basisA = calcLineBasis(icurveA?.params as ILineParams, itempos);

      const basisB = calcLineBasis(icurveB?.params as ILineParams, targetpos);

      return [
        {
          drawTo: targetpos,
          offset: basisA.add(basisB).normalize().toArray(),
        },
      ];
    }

    return [
      {
        drawTo: itempos,
        offset:
          this.getOffsetOfICurve(constraint.itemid, itempos) ??
          this.getOffsetOfICurve(constraint.target, targetpos),
      },
    ];
  };

  // 按约束新增标签渲染
  // 约束图标渲染逻辑：
  // 每个约束图标均有两个位置属性
  // 一个是该图标指示的位置，称为指示点，由后端返回
  // 另一个是该图标实际所处的位置，成为渲染点，由前端计算
  // 渲染点和指示点一定有偏离，根据不同约束，其偏离方式也有差异
  // 一些情况下，渲染点固定在视图上，指示点的上方，这种情况通过修改texture来达成
  // 另一种情况，渲染点占据以指示点为原点的坐标系内“固定大小”的位置，通过keepSizeObject来达成
  private addConstraint(constraint: ConstraintData) {
    if (!FeatureManager.isFeatureEnabled('sketch-constraint-icon')) {
      return;
    }

    if (
      [constraint.itemid, constraint.target].every((id) =>
        basicSketchIds.some((i) => id?.startsWith(i)),
      )
    ) {
      return;
    }

    if (constraint.implicit) {
      return;
    }

    if (constraint.itempos == null && constraint.targetpos == null) {
      return;
    }

    // 若约束包含隐藏对象，则不显示
    if (
      !(
        this.renderDataByICurve.get(constraint.target)?.icurve?.visible &&
        this.renderDataByICurve.get(constraint.itemid)?.icurve?.visible
      )
    ) {
      return;
    }

    const iCurveId = constraint.parent.id;
    if (this.renderDataByICurve.get(iCurveId) == null) {
      this.renderDataByICurve.set(iCurveId, {
        icurve: constraint.parent,
        constraints: [],
      });
    }
    this.renderDataByICurve.get(iCurveId).constraints.push(constraint);

    const drawTos = this.getDrawToAndOffsets(constraint);

    drawTos.forEach(({ drawTo, offset }) => {
      // 并不是所有约束都有标签，pos没有就说明不渲染
      if (drawTo == null) {
        return;
      }

      const renderOffset = offset ?? [0, 1];

      const newData: ConstraintTag = {
        constraint,
        position: drawTo,
        offset: renderOffset,
      };

      const key = `${drawTo.map((v) => v.toFixed(8)).join('_')}-${renderOffset.map((v) => (v * 100).toFixed(8)).join('_')}`;

      if (this.renderDataByRenderTarget.get(key) == null) {
        this.renderDataByRenderTarget.set(key, { data: [] });
      }
      this.renderDataByRenderTarget.get(key).data.push(newData);
      this.reRenderKeys.add(key);
    });
  }
  // 按约束移除标签渲染
  removeConstraint(constraint: ConstraintData, needRerender = false) {
    // 先从源数据表下移除
    const parentData = this.renderDataByICurve.get(constraint.parent.id);

    if (parentData != null) {
      this.renderDataByICurve.set(constraint.parent.id, {
        icurve: parentData.icurve,
        constraints: parentData.constraints.filter(
          (c) => !this.isSameConstraint(constraint, c),
        ),
      });
    }

    // 再从渲染表下移除
    Array.from(this.renderDataByRenderTarget.keys()).forEach((key) => {
      const value = this.renderDataByRenderTarget.get(key);
      const needUpdate = value.data.some((c) =>
        this.isSameConstraint(constraint, c.constraint),
      );

      if (needUpdate) {
        this.renderDataByRenderTarget.set(key, {
          data: value.data.filter(
            (c) => !this.isSameConstraint(constraint, c.constraint),
          ),
          renderObj: value.renderObj,
        });
        // 标记为待更新
        this.reRenderKeys.add(key);
      }
    });

    if (needRerender) {
      this.rerender();
    }
  }

  updateICurves(icurves: ICurve[]) {
    // 被删除的+被更新的
    const removed: ConstraintData[] = [];
    // 新增的+被更新的
    const added: ConstraintData[] = [];

    const fullData = Array.from(this.renderDataByICurve.values()).flatMap(
      (d) => d.constraints,
    );

    // 检查有变更的2d元素下的约束
    icurves.forEach((icurve) => {
      const oldData = this.renderDataByICurve.get(icurve.id);

      // 更新icurve
      this.renderDataByICurve.set(icurve.id, {
        icurve,
        constraints: oldData?.constraints ?? [],
      });

      const newData = icurve.constraints as Constraint[];

      // 旧数据中，找到被删除的和被更新的
      fullData?.forEach((old) => {
        const newVal = newData?.find((n) => this.isSameConstraint(n, old));

        if (newVal == null) {
          return;
        }

        const unChanged = isEqual(newVal, old);

        if (unChanged) {
          return;
        }

        removed.push(old);
        added.push({ ...newVal, parent: icurve });
      });

      // 新数据中新出现的
      added.push(
        ...newData
          .filter((newData) =>
            fullData.every((old) => !this.isSameConstraint(newData, old)),
          )
          .map((c) => ({ ...c, parent: icurve })),
      );
    });

    // 先移除再添加
    removed.forEach((v) => this.removeConstraint(v));
    added.forEach((v) => this.addConstraint(v));

    this.rerender();
  }
}

export const constraintIconRenderer = new ConstraintIconRenderer();
